import groovy.sql.Sql
import com.alibaba.fastjson.JSON

class SpecExecutor {
	private static Map genError(String phrase, Exception ex){
		return [phrase: phrase, ex: ex]
	}

	private static Sql genSql(){
		def one = SpecConfig.props
		return Sql.newInstance(one['jdbc.url'], one['jdbc.user'], 
			one['jdbc.pwd'], one['jdbc.driver'])
	}

	private static void exeSql(String sql){
		def db = genSql()
		try{
			db.withTransaction{
				sql.split(';').each{oneSql ->
					db.execute(oneSql)
				}
			}
		}finally{
			db.close()
		}
	}

	private static Map prepareSql(String phrase, Map one){
		if(!one || (!one.sql && !one.sqlFile))
			return [:]

		def sb = new StringBuffer()
		sb << one.sql.join('')
		if(one.sqlFile){
			for(file in one.sqlFile)
				sb << SpecConfig.getFile(file).text
		}

		try{
			exeSql(sb.toString())
			return [:]
		}catch(ex){
			return genError(phrase, ex)
		}
	}

	private static Map prepareRequest(String phrase, Map one, SpecHttpClient httpclient){
		if(!one || !one.requestFile)
			return [:]

		Map r = [:]
		int i = 0
		for(file in one.requestFile){
			def item = SpecParser.toKeyValue(SpecConfig.getFile(file).readLines())

			def headers = [:]
			if('post' == item.method){
				item.params = [json: item.params]
				headers['Content-Type'] = 'application/json'
			}else{
				if(item.params){
					def paramsTransfered = [:]
					item.params.split(';').each{
						def arr = it.split(':')
						paramsTransfered[arr[0].trim()] = arr[1].trim()
					}
					item.params = ['key=value': paramsTransfered]
				}

				if('postform' == item.method){
					item.method = 'post'
				}
			}

			def result = httpclient.doRequest(item.url, item.method, headers, item.params ?: [:])
			if(result.ex){
				result.phrase = phrase
				return result
			}

			r['prepareRequestData' + i] = result.data
			i++
		}

		return r
	}

	private static String replacePrepareRequestResult(String str, Map resultData){
		if(!str)
			return null

		if(!resultData)
			return str

		// str -> eg. {"name": "${r0.name}", "age": 15}
		Map values = [:]
		resultData.each{k, v ->
			values[k.replace('prepareRequestData', 'r')] = v
		}
		
		SpecScriptEval.eval('"""' + str + '"""', values)
	}

	public static Map exe(Map item){
		def setupResult = prepareSql('setup', item.setup)
		if(setupResult.ex)
			return setupResult
		SpecLogger.debug('Prepare sql done : ' + item.des)

		def httpclient = SpecHttpClient.newInstance()

		def setupRequestResult = prepareRequest('setup', item.setup, httpclient)
		if(setupRequestResult.ex)
			return setupRequestResult
		SpecLogger.debug('Prepare http request done : ' + item.des)

		if(item.params['key=value']){
			String tmp = JSON.toJSONString(item.params['key=value'])
			tmp = replacePrepareRequestResult(tmp, setupRequestResult)
			item.params['key=value'] = JSON.parse(tmp)
		}
		item.params['json'] = replacePrepareRequestResult(item.params['json'], setupRequestResult)

		def result = httpclient.doRequest(item.url, item.method, item.headers, item.params ?: [:])

		def teardownResult = prepareSql('teardown', item.teardown)
		if(teardownResult.ex)
			return teardownResult
		SpecLogger.debug('After execute sql done : ' + item.des)

		def teardownRequestResult = prepareRequest('teardown', item.teardown, httpclient)
		if(teardownRequestResult.ex)
			return teardownRequestResult
		SpecLogger.debug('After execute http request done : ' + item.des)

		if(result.ex)
			return result

		if(item.expects)
			return expect(result.data, result.responseText, item.expects.asserts)
		else
			return [:]
	}

	private static Map expect(Object data, String responseText, List asserts){
		def rr = [:]

		rr.assertFail = false
		rr.assertList = []

		def binding = [data: data, responseText: responseText]
		for(one in asserts){
			def isOk = SpecScriptEval.eval(one, binding)
			def assertResult = [assertResult: isOk, assertExpression: one]
			rr.assertList << assertResult

			if(!isOk){
				rr.assertFail = true

				def oneExpression = SpecScriptEval.parseAssert(one)
				if(oneExpression){
					assertResult.realResult = SpecScriptEval.eval(oneExpression.expression, binding)
					assertResult.expectResult = oneExpression.value
				}
			}
		}

		rr
	}
}
